/*
 * Copyright 2019 Google Inc. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

import {
  TwaManifest,
  TwaManifestJson,
  asDisplayMode,
  resolveDisplayOverride,
} from '../../lib/TwaManifest';
import {WebManifestJson} from '../../lib/types/WebManifest';
import Color = require('color');
import {ShortcutInfo} from '../../lib/ShortcutInfo';

describe('TwaManifest', () => {
  describe('#fromWebManifestJson', () => {
    it('creates a correct TWA manifest', () => {
      const manifest = {
        'name': 'PWA Directory',
        'short_name': 'PwaDirectory',
        'start_url': '/?utm_source=homescreen',
        'display': 'fullscreen',
        'orientation': 'landscape',
        'icons': [{
          'src': '/favicons/android-chrome-192x192.png',
          'sizes': '192x192',
          'type': 'image/png',
        }, {
          'src': '/favicons/android-chrome-512x512.png',
          'sizes': '512x512',
          'type': 'image/png',
        }],
        'theme_color': '#00ff00',
        'background_color': '#7cc0ff',
        'shortcuts': [{
          'name': 'shortcut name',
          'short_name': 'short',
          'url': '/launch',
          'icons': [{
            'src': '/shortcut_icon.png',
            'sizes': '96x96',
          }],
        }],
        'file_handlers': [{
          'action': '/',
          'accept': {
            'text/plain': ['.txt'],
            'image/jpeg': ['.jpg', 'jpeg'],
          },
        }],
      };
      const manifestUrl = new URL('https://pwa-directory.com/manifest.json');
      const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest as WebManifestJson);
      expect(twaManifest.packageId).toBe('com.pwa_directory.twa');
      expect(twaManifest.name).toBe('PWA Directory');
      expect(twaManifest.launcherName).toBe('PwaDirectory');
      expect(twaManifest.display).toBe('fullscreen');
      expect(twaManifest.orientation).toBe('landscape');
      expect(twaManifest.startUrl).toBe('/?utm_source=homescreen');
      expect(twaManifest.iconUrl)
          .toBe('https://pwa-directory.com/favicons/android-chrome-512x512.png');
      expect(twaManifest.maskableIconUrl).toBeUndefined();
      expect(twaManifest.monochromeIconUrl).toBeUndefined();
      expect(twaManifest.themeColor.hex()).toBe('#00FF00');
      expect(twaManifest.themeColorDark.hex()).toBe('#000000');
      expect(twaManifest.navigationColor.hex()).toBe('#000000');
      expect(twaManifest.navigationColorDark.hex()).toBe('#000000');
      expect(twaManifest.navigationDividerColor.hex()).toBe('#000000');
      expect(twaManifest.navigationDividerColorDark.hex()).toBe('#000000');
      expect(twaManifest.backgroundColor.hex()).toBe('#7CC0FF');
      expect(twaManifest.appVersionName).toBe('1');
      expect(twaManifest.appVersionCode).toBe(1);
      expect(twaManifest.signingKey.path).toBe('./android.keystore');
      expect(twaManifest.signingKey.alias).toBe('android');
      expect(twaManifest.splashScreenFadeOutDuration).toBe(300);
      expect(twaManifest.enableNotifications).toBeTrue();
      expect(twaManifest.webManifestUrl).toEqual(manifestUrl);
      expect(twaManifest.shortcuts.length).toBe(1);
      expect(twaManifest.shortcuts[0].name).toBe('shortcut name');
      expect(twaManifest.shortcuts[0].shortName).toBe('short');
      expect(twaManifest.shortcuts[0].url).toBe('https://pwa-directory.com/launch');
      expect(twaManifest.shortcuts[0].chosenIconUrl)
          .toBe('https://pwa-directory.com/shortcut_icon.png');
      expect(twaManifest.generateShortcuts())
          .toBe('[[name:\'shortcut name\', short_name:\'short\',' +
            ' url:\'https://pwa-directory.com/launch\', icon:\'shortcut_0\']]');
      expect(twaManifest.fallbackType).toBe('customtabs');
      expect(twaManifest.fileHandlers).toEqual([
        {
          'actionUrl': 'https://pwa-directory.com/',
          'mimeTypes': ['text/plain', 'image/jpeg'],
        },
      ]);
    });

    it('Sets correct defaults for unavailable fields', () => {
      const manifest = {};
      const manifestUrl = new URL('https://pwa-directory.com/manifest.json');
      const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest);
      expect(twaManifest.packageId).toBe('com.pwa_directory.twa');
      expect(twaManifest.host).toBe('pwa-directory.com');
      expect(twaManifest.name).toBe('My TWA');
      expect(twaManifest.launcherName).toBe('My TWA');
      expect(twaManifest.startUrl).toBe('/');
      expect(twaManifest.iconUrl).toBeUndefined();
      expect(twaManifest.maskableIconUrl).toBeUndefined();
      expect(twaManifest.monochromeIconUrl).toBeUndefined();
      expect(twaManifest.display).toBe('standalone');
      expect(twaManifest.orientation).toBe('default');
      expect(twaManifest.themeColor.hex()).toBe('#FFFFFF');
      expect(twaManifest.themeColorDark.hex()).toBe('#000000');
      expect(twaManifest.navigationColor.hex()).toBe('#000000');
      expect(twaManifest.navigationColorDark.hex()).toBe('#000000');
      expect(twaManifest.navigationDividerColor.hex()).toBe('#000000');
      expect(twaManifest.navigationDividerColorDark.hex()).toBe('#000000');
      expect(twaManifest.backgroundColor.hex()).toBe('#FFFFFF');
      expect(twaManifest.appVersionName).toBe('1');
      expect(twaManifest.appVersionCode).toBe(1);
      expect(twaManifest.signingKey.path).toBe('./android.keystore');
      expect(twaManifest.signingKey.alias).toBe('android');
      expect(twaManifest.splashScreenFadeOutDuration).toBe(300);
      expect(twaManifest.enableNotifications).toBeTrue();
      expect(twaManifest.webManifestUrl).toEqual(manifestUrl);
      expect(twaManifest.shortcuts).toEqual([]);
      expect(twaManifest.generateShortcuts()).toBe('[]');
      expect(twaManifest.additionalTrustedOrigins).toEqual([]);
      expect(twaManifest.retainedBundles).toEqual([]);
    });

    it('Uses "name" when "short_name" is not available', () => {
      const manifest = {
        'name': 'PWA Directory',
      };
      const manifestUrl = new URL('https://pwa-directory.com/manifest.json');
      const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest);
      expect(twaManifest.name).toBe('PWA Directory');
      expect(twaManifest.launcherName).toBe('PWA Director');
    });

    it('Ignores shortcuts if icon size is empty or too small', () => {
      const manifest = {
        'shortcuts': [{
          'name': 'invalid',
          'url': '/invalid',
          'icons': [{
            'src': '/no_size.png',
          }, {
            'src': '/small_size.png',
            'sizes': '95x95',
          }],
        }],
      };
      const manifestUrl = new URL('https://pwa-directory.com/manifest.json');
      const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest);
      expect(twaManifest.shortcuts).toEqual([]);
      expect(twaManifest.generateShortcuts()).toBe('[]');
    });

    it('resolves URLs for maskable and monochrome icons', () => {
      const manifest = {
        'name': 'PWA Directory',
        'short_name': 'PwaDirectory',
        'start_url': '/?utm_source=homescreen',
        'icons': [{
          'src': '/favicons/any.png',
          'sizes': '512x512',
          'type': 'image/png',
          'purpose': 'any',
        }, {
          'src': '/favicons/maskable.png',
          'sizes': '512x512',
          'type': 'image/png',
          'purpose': 'maskable',
        }, {
          'src': '/favicons/monochrome.png',
          'sizes': '512x512',
          'type': 'image/png',
          'purpose': 'monochrome',
        }],
      };
      const manifestUrl = new URL('https://pwa-directory.com/manifest.json');
      const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest);
      expect(twaManifest.packageId).toBe('com.pwa_directory.twa');
      expect(twaManifest.name).toBe('PWA Directory');
      expect(twaManifest.launcherName).toBe('PwaDirectory');
      expect(twaManifest.startUrl).toBe('/?utm_source=homescreen');
      expect(twaManifest.iconUrl)
          .toBe('https://pwa-directory.com/favicons/any.png');
      expect(twaManifest.maskableIconUrl).toBe('https://pwa-directory.com/favicons/maskable.png');
      expect(twaManifest.monochromeIconUrl).toBe('https://pwa-directory.com/favicons/monochrome.png');
    });
  });

  describe('#constructor', () => {
    it('Builds a TwaManifest correctly', () => {
      const twaManifestJson = {
        packageId: 'com.pwa_directory.twa',
        host: 'pwa-directory.com',
        name: 'PWA Directory',
        launcherName: 'PwaDirectory',
        startUrl: '/',
        iconUrl: 'https://pwa-directory.com/favicons/android-chrome-512x512.png',
        display: 'fullscreen',
        displayOverride: ['window-controls-overlay'],
        orientation: 'landscape',
        themeColor: '#00ff00',
        themeColorDark: '#000000',
        navigationColor: '#000000',
        navigationColorDark: '#ffffff',
        navigationDividerColor: '#ff0000',
        navigationDividerColorDark: '#dddddd',
        backgroundColor: '#0000ff',
        appVersion: '1.0.0',
        appVersionCode: 10,
        signingKey: {
          path: './my-keystore',
          alias: 'my-alias',
        },
        splashScreenFadeOutDuration: 300,
        enableNotifications: true,
        shortcuts: [{name: 'name', shortName: 'shortName', url: '/', chosenIconUrl: 'icon.png'}],
        webManifestUrl: 'https://pwa-directory.com/manifest.json',
        generatorApp: 'test',
        fallbackType: 'webview',
        enableSiteSettingsShortcut: false,
        isChromeOSOnly: false,
        isMetaQuest: false,
        serviceAccountJsonFile: '/home/service-account.json',
        additionalTrustedOrigins: ['test.com'],
        retainedBundles: [3, 4, 5],
      } as TwaManifestJson;
      const twaManifest = new TwaManifest(twaManifestJson);
      expect(twaManifest.packageId).toEqual(twaManifestJson.packageId);
      expect(twaManifest.host).toEqual(twaManifestJson.host);
      expect(twaManifest.name).toEqual(twaManifestJson.name);
      expect(twaManifest.launcherName).toEqual(twaManifest.launcherName);
      expect(twaManifest.startUrl).toEqual(twaManifest.startUrl);
      expect(twaManifest.iconUrl).toEqual(twaManifest.iconUrl);
      expect(twaManifest.display).toEqual('fullscreen');
      expect(twaManifest.displayOverride).toEqual(['window-controls-overlay']);
      expect(twaManifest.orientation).toEqual('landscape');
      expect(twaManifest.themeColor).toEqual(new Color('#00ff00'));
      expect(twaManifest.themeColorDark).toEqual(new Color('#000000'));
      expect(twaManifest.navigationColor).toEqual(new Color('#000000'));
      expect(twaManifest.navigationColorDark).toEqual(new Color('#ffffff'));
      expect(twaManifest.navigationDividerColor).toEqual(new Color('#ff0000'));
      expect(twaManifest.navigationDividerColorDark).toEqual(new Color('#dddddd'));
      expect(twaManifest.backgroundColor).toEqual(new Color('#0000ff'));
      expect(twaManifest.appVersionName).toEqual(twaManifestJson.appVersion);
      expect(twaManifest.appVersionCode).toEqual(twaManifestJson.appVersionCode!);
      expect(twaManifest.signingKey.path).toEqual(twaManifestJson.signingKey.path);
      expect(twaManifest.signingKey.alias).toEqual(twaManifestJson.signingKey.alias);
      expect(twaManifest.splashScreenFadeOutDuration)
          .toEqual(twaManifestJson.splashScreenFadeOutDuration);
      expect(twaManifest.enableNotifications).toEqual(twaManifestJson.enableNotifications);
      expect(twaManifest.shortcuts)
          .toEqual([new ShortcutInfo('name', 'shortName', '/', 'icon.png')]);
      expect(twaManifest.webManifestUrl).toEqual(new URL(twaManifestJson.webManifestUrl!));
      expect(twaManifest.generatorApp).toEqual(twaManifestJson.generatorApp!);
      expect(twaManifest.fallbackType).toBe('webview');
      expect(twaManifest.enableSiteSettingsShortcut).toEqual(false);
      expect(twaManifest.isChromeOSOnly).toEqual(false);
      expect(twaManifest.isMetaQuest).toEqual(false);
      expect(twaManifest.serviceAccountJsonFile).toEqual(twaManifestJson.serviceAccountJsonFile);
      expect(twaManifest.additionalTrustedOrigins).toEqual(['test.com']);
      expect(twaManifest.retainedBundles).toEqual([3, 4, 5]);
    });

    it('Sets correct default values for optional fields', () => {
      const twaManifestJson = {
        packageId: 'com.pwa_directory.twa',
        host: 'pwa-directory.com',
        name: 'PWA Directory',
        launcherName: 'PwaDirectory',
        startUrl: '/',
        iconUrl: 'https://pwa-directory.com/favicons/android-chrome-512x512.png',
        themeColor: '#00ff00',
        navigationColor: '#000000',
        backgroundColor: '#0000ff',
        appVersion: '1.0.0',
        appVersionCode: 10,
        signingKey: {
          path: './my-keystore',
          alias: 'my-alias',
        },
        splashScreenFadeOutDuration: 300,
        enableNotifications: true,
        shortcuts: [{name: 'name', url: '/', chosenIconUrl: 'icon.png'}],
        generatorApp: 'test',
      } as TwaManifestJson;
      const twaManifest = new TwaManifest(twaManifestJson);
      expect(twaManifest.webManifestUrl).toBeUndefined();
      expect(twaManifest.fallbackType).toBe('customtabs');
      expect(twaManifest.display).toBe('standalone');
      expect(twaManifest.enableSiteSettingsShortcut).toEqual(true);
      expect(twaManifest.navigationColor).toEqual(new Color('#000000'));
      expect(twaManifest.navigationDividerColor).toEqual(new Color('#00000000'));
      expect(twaManifest.navigationDividerColorDark).toEqual(new Color('#000000'));
    });
  });

  describe('#validate', () => {
    it('Returns false for an empty TWA Manifest', () => {
      const twaManifest = new TwaManifest({} as TwaManifestJson);
      expect(twaManifest.validate()).not.toBeNull();
    });

    it('Returns true a correct TWA Manifest', () => {
      const twaManifest = new TwaManifest({
        packageId: 'com.pwa_directory.twa',
        host: 'pwa-directory.com',
        name: 'PWA Directory',
        launcherName: 'PwaDirectory',
        startUrl: '/',
        iconUrl: 'https://pwa-directory.com/favicons/android-chrome-512x512.png',
        themeColor: '#00ff00',
        navigationColor: '#000000',
        backgroundColor: '#0000ff',
        appVersion: '1.0.0',
        signingKey: {
          path: './android-keystore',
          alias: 'android',
        },
        splashScreenFadeOutDuration: 300,
        enableNotifications: true,
        shortcuts: [{name: 'name', url: '/', chosenIconUrl: 'icon.png'}],
      } as TwaManifestJson);
      expect(twaManifest.validate()).toBeNull();
    });
  });

  describe('#asDisplayMode', () => {
    it('Returns display mode if it is supported', () => {
      expect(asDisplayMode('standalone')).toBe('standalone');
      expect(asDisplayMode('fullscreen')).toBe('fullscreen');
      expect(asDisplayMode('minimal-ui')).toBe('minimal-ui');
      expect(asDisplayMode('browser')).toBe('browser');
    });

    it('Returns null for unsupported display modes', () => {
      expect(asDisplayMode('bogus')).toBeNull();
      expect(asDisplayMode('')).toBeNull();
    });
  });

  describe('#resolveDisplayOverride', () => {
    it('Keeps display override values that are supported', () => {
      expect(resolveDisplayOverride([
        'browser',
        'fullscreen',
        'minimal-ui',
        'standalone',
        'window-controls-overlay',
        'tabbed',
      ])).toEqual([
        'browser',
        'fullscreen',
        'minimal-ui',
        'standalone',
        'window-controls-overlay',
        'tabbed',
      ]);
    });

    it('Ignore unsupported values', () => {
      expect(resolveDisplayOverride([
        'browser',
        // @ts-expect-error Unsupported value for testing
        'not-supported',
      ])).toEqual(['browser']);
      expect(resolveDisplayOverride([
        // @ts-expect-error Unsupported value for testing
        'not-supported',
      ])).toEqual([]);
    });
  });

  describe('#merge', () => {
    it('Validates that the merge is done correctly in case which' +
        ' there are no fields to ignore', async () => {
      const webManifest: WebManifestJson = {
        'display': 'fullscreen',
        'name': 'name',
        'short_name': 'different_name',
        'start_url': 'https://name.github.io/',
        'icons': [{
          'src': 'https://image.png',
          'sizes': '512x512',
          'purpose': 'any',
        },
        ],
        'protocol_handlers': [{
          'protocol': 'web+test-replace',
          'url': 'test-format-web/%s',
        }],
        'file_handlers': [{
          'action': '/',
          'accept': {
            'text/plain': ['.txt'],
            'image/jpeg': ['.jpg', 'jpeg'],
          },
        }],
      };
      const twaManifest = new TwaManifest({
        'packageId': 'id',
        'launchHandlerClientMode': 'navigate_existing',
        'host': 'host',
        'name': 'name',
        'launcherName': 'name',
        'display': 'standalone',
        'displayOverride': ['window-controls-overlay'],
        'themeColor': '#FFFFFF',
        'themeColorDark': '#000000',
        'navigationColor': '#000000',
        'navigationColorDark': '#000000',
        'navigationDividerColor': '#000000',
        'navigationDividerColorDark': '#000000',
        'backgroundColor': '#FFFFFF',
        'enableNotifications': false,
        // The start_urls are different, but since they both resolve the same relative
        // to the host url, nothing changes.
        'startUrl': '/',
        'iconUrl': 'https://image.png/',
        'splashScreenFadeOutDuration': 300,
        'signingKey': {
          'alias': 'android',
          'path': './android.keystore',
        },
        'appVersionCode': 1,
        'shortcuts': [],
        'generatorApp': 'bubblewrap-cli',
        'webManifestUrl': 'https://name.github.io/manifest.json',
        'fallbackType': 'customtabs',
        'features': {},
        'enableSiteSettingsShortcut': true,
        'isChromeOSOnly': false,
        'isMetaQuest': false,
        'fullScopeUrl': 'https://name.github.io/',
        'appVersion': '1',
        'serviceAccountJsonFile': '/home/service-account.json',
        'protocolHandlers': [
          {
            'protocol': 'web+test-replace',
            'url': 'test-format-twa/%s',
          },
          {
            'protocol': 'web+test-keep',
            'url': 'test-format-twa/%s',
          },
        ],
      });
      // The versions shouldn't change because the update happens in `cli`.
      const expectedTwaManifest = new TwaManifest({
        ...twaManifest.toJson(),
        'launcherName': 'different_name',
        'display': 'fullscreen',
        'displayOverride': [],
        'protocolHandlers': [
          {
            'protocol': 'web+test-replace',
            'url': 'test-format-web/%s',
          },
          {
            'protocol': 'web+test-keep',
            'url': 'test-format-twa/%s',
          },
        ],
        'fileHandlers': [
          {
            'actionUrl': 'https://name.github.io/',
            'mimeTypes': ['text/plain', 'image/jpeg'],
          },
        ],
      });
      // A URL to insert as the webManifestUrl.
      const url = new URL('https://name.github.io/manifest.json');
      expect(await TwaManifest.merge([], url, webManifest, twaManifest))
          .toEqual(expectedTwaManifest);
    });
    it('Validates that the merge is done correctly in case which' +
      ' there are fields to ignore', async () => {
      const webManifest: WebManifestJson = {
        'display': 'fullscreen',
        'name': 'name',
        'short_name': 'different_name',
        'start_url': 'https://other_url.github.io/',
        'icons': [{
          'src': 'https://image.png',
          'sizes': '512x512',
          'purpose': 'any',
        },
        ],
        'protocol_handlers': [{
          'protocol': 'web+test-replace',
          'url': 'test-format-web/%s',
        }],
      };
      const twaManifest = new TwaManifest({
        'packageId': 'id',
        'launchHandlerClientMode': 'navigate_existing',
        'host': 'host',
        'name': 'name',
        'launcherName': 'name',
        'display': 'standalone',
        'themeColor': '#FFFFFF',
        'themeColorDark': '#000000',
        'navigationColor': '#000000',
        'navigationColorDark': '#000000',
        'navigationDividerColor': '#000000',
        'navigationDividerColorDark': '#000000',
        'backgroundColor': '#FFFFFF',
        'enableNotifications': false,
        // The start_urls are different, but since they both resolve the same relative
        // to the host url, nothing changes.
        'startUrl': '/',
        'iconUrl': 'https://image.png/',
        'splashScreenFadeOutDuration': 300,
        'signingKey': {
          'alias': 'android',
          'path': './android.keystore',
        },
        'appVersionCode': 1,
        'shortcuts': [],
        'generatorApp': 'bubblewrap-cli',
        'webManifestUrl': 'https://name.github.io/manifest.json',
        'fallbackType': 'customtabs',
        'features': {},
        'enableSiteSettingsShortcut': true,
        'isChromeOSOnly': false,
        'isMetaQuest': false,
        'fullScopeUrl': 'https://name.github.io/',
        'appVersion': '1',
        'serviceAccountJsonFile': '/home/service-account.json',
        'protocolHandlers': [
          {
            'protocol': 'web+test-replace',
            'url': 'test-format-twa/%s',
          },
          {
            'protocol': 'web+test-keep',
            'url': 'test-format-twa/%s',
          },
        ],
      });
      // The versions shouldn't change because the update happens in `cli`.
      const expectedTwaManifest = twaManifest;
      // A URL to insert as the webManifestUrl.
      const url = new URL('https://name.github.io/manifest.json');
      expect(await TwaManifest.merge(
          ['short_name', 'display', 'protocol_handlers'],
          url,
          webManifest,
          twaManifest,
      )).toEqual(expectedTwaManifest);
    });
  });
});
